package com.boilermaker.lotterysystem.service.impl;

import com.boilermaker.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.boilermaker.lotterysystem.common.exception.ServiceException;
import com.boilermaker.lotterysystem.common.utils.JacksonUtil;
import com.boilermaker.lotterysystem.common.utils.RedisUtil;
import com.boilermaker.lotterysystem.controller.param.ActivityPrizeParam;
import com.boilermaker.lotterysystem.controller.param.ActivityUserParam;
import com.boilermaker.lotterysystem.controller.param.CreateActivityParam;
import com.boilermaker.lotterysystem.controller.param.PageParam;
import com.boilermaker.lotterysystem.dao.dataobject.ActivityDO;
import com.boilermaker.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.boilermaker.lotterysystem.dao.dataobject.ActivityUserDO;
import com.boilermaker.lotterysystem.dao.dataobject.PrizeDO;
import com.boilermaker.lotterysystem.dao.mapper.*;
import com.boilermaker.lotterysystem.service.ActivityService;
import com.boilermaker.lotterysystem.service.dto.*;
import com.boilermaker.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.boilermaker.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.boilermaker.lotterysystem.service.enums.ActivityStatusEnum;
import com.boilermaker.lotterysystem.service.enums.ActivityUserStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ActivityServiceImpl implements ActivityService {

    private static final String REDIS_ACTIVITY_PREFIX = "activity_";

    private static final Long REDIS_ACTIVITY_TIMEOUT = 60 * 60 * 24 * 3L; // 活动缓存时间 3 天

    @Autowired
    private ActivityMapper activityMapper;

    @Autowired
    private PrizeMapper prizeMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private ActivityUserMapper activityUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 创建活动
     *
     * @param param
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CreateActivityDTO createActivity(CreateActivityParam param) {

        // 校验参数有效性
        checkActivityInfo(param);

        /*
         * 落库
         */

        // 保存活动基本信息
        ActivityDO activityDO = new ActivityDO();
        activityDO.setActivityName(param.getActivityName());
        activityDO.setDescription(param.getDescription());
        activityDO.setStatus(ActivityStatusEnum.RUNNING.name());

        activityMapper.insert(activityDO);

        // 保存活动关联的奖品
        List<ActivityPrizeParam> activityPrizeList = param.getActivityPrizeList();
        List<ActivityPrizeDO> activityPrizeDOList = activityPrizeList.stream()
                .map(prizeParam -> { // List<ActivityPrizeParam> -> List<ActivityPrizeDO>

                    ActivityPrizeDO activityPrizeDO = new ActivityPrizeDO();
                    activityPrizeDO.setActivityId(activityDO.getId());
                    activityPrizeDO.setPrizeId(prizeParam.getPrizeId());
                    activityPrizeDO.setPrizeAmount(prizeParam.getPrizeAmount());
                    activityPrizeDO.setPrizeTiers(prizeParam.getPrizeTiers());
                    activityPrizeDO.setStatus(ActivityPrizeStatusEnum.AVAILABLE.name()); // 奖品初始状态设为 AVAILABLE
                    return activityPrizeDO;

                }).collect(Collectors.toList());

        activityPrizeMapper.batchInsert(activityPrizeDOList);

        // 保存活动关联的人员
        List<ActivityUserParam> activityUserList = param.getActivityUserList();
        List<ActivityUserDO> activityUserDOList = activityUserList.stream()
                .map(userParam -> { // List<ActivityUserParam> -> List<ActivityUserDO>

                    ActivityUserDO activityUserDO = new ActivityUserDO();
                    activityUserDO.setActivityId(activityDO.getId());
                    activityUserDO.setUserId(userParam.getUserId());
                    activityUserDO.setUserName(userParam.getUserName());
                    activityUserDO.setStatus(ActivityUserStatusEnum.AVAILABLE.name()); // 人员初始状态设为 AVAILABLE
                    return activityUserDO;

                }).collect(Collectors.toList());

        activityUserMapper.batchInsert(activityUserDOList);

        /*
         * 缓存
         */

        // 获取奖品的基础信息
        List<Long> prizeIds = activityPrizeList.stream()
                .map(ActivityPrizeParam::getPrizeId)
                .distinct()
                .toList();
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);

        // 将基础 DO 整合为含有完整活动信息的 DTO
        ActivityDetailDTO activityDetailDTO = buildActivityDTO(
                activityDO, prizeDOList, activityPrizeDOList, activityUserDOList);

        // 缓存 DTO
        cacheActivity(activityDetailDTO);

        // 构造返回
        CreateActivityDTO createActivityDTO = new CreateActivityDTO();
        createActivityDTO.setActivityId(activityDO.getId());
        return createActivityDTO;
    }

    /**
     * 获取活动列表，支持翻页功能
     *
     * @param param
     * @return
     */
    @Override
    public PageListDTO<ActivityDTO> findActivityList(PageParam param) {

        List<ActivityDO> activityDOList = activityMapper.selectActivityList(
                param.offset(), param.getPageSize());

        PageListDTO<ActivityDTO> pageListDTO = new PageListDTO<>();

        pageListDTO.setTotal(activityMapper.count());
        pageListDTO.setRecords(
                activityDOList.stream().map(activityDO -> {

                    ActivityDTO dto = new ActivityDTO();
                    dto.setActivityId(activityDO.getId());
                    dto.setActivityName(activityDO.getActivityName());
                    dto.setDescription(activityDO.getDescription());
                    dto.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));

                    return dto;
                }).collect(Collectors.toList())
        );

        return pageListDTO;
    }

    /**
     * 获取指定 ID 活动的完整描述信息（先查缓存，若不存在则查数据库，再重新尝试缓存）
     */
    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {

        if (activityId == null) {
            log.warn("查询活动详细信息失败，因为参数为空");
            return null;
        }

        // 先查缓存
        ActivityDetailDTO detailDTO = getActivityDetailFromCacheOnly(activityId);

        // 若获取成功直接返回
        if (detailDTO != null) {
            log.info("查询到的活动详细信息: detailDTO={}", JacksonUtil.writeValueAsString(detailDTO));
            return detailDTO;
        }

        // 若获取失败
        detailDTO = getActivityDetailDTO(activityId);
        boolean ok = cacheActivity(detailDTO);

        if (!ok) {
            log.warn("获取指定 ID 活动的完整描述信息时，尝试缓存失败");
        }

        return detailDTO;
    }

    /**
     * 缓存活动完整描述信息（该方法会在内部处理异常，不会抛出而导致事务回滚）<P>
     * key:ACTIVITY_activityId, value:ActivityDetailDTO(JSON)
     */
    public boolean cacheActivity(ActivityDetailDTO activityDetailDTO) {

        if (activityDetailDTO == null || activityDetailDTO.getActivityId() == null) {
            log.warn("缓存失败，因为参数为空");
            return false;
        }

        String key = REDIS_ACTIVITY_PREFIX + activityDetailDTO.getActivityId();
        String val = JacksonUtil.writeValueAsString(activityDetailDTO);
        try {
            redisUtil.set(key, val, REDIS_ACTIVITY_TIMEOUT);
            return true;
        } catch (Exception e) {
            log.error("缓存失败, key={}, val={}", key, val);
            return false;
        }
    }

    /**
     * 获取指定 ID 活动的完整描述信息（仅查缓存）
     */
    public ActivityDetailDTO getActivityDetailFromCacheOnly(Long activityId) {

        if (activityId == null) {
            log.warn("获取缓存数据失败，因为参数为空");
            return null;
        }

        String key = REDIS_ACTIVITY_PREFIX + activityId;
        try {
            String val = redisUtil.get(key);

            if (!StringUtils.hasText(val)) {
                log.warn("获取缓存数据失败，因为活动信息不存在，key = {}", key);
                return null;
            }
            return JacksonUtil.readValue(val, ActivityDetailDTO.class);
        } catch (Exception e) {
            log.error("获取缓存数据失败，key = {}", key, e);
            return null;
        }
    }

    /**
     * 根据活动 ID 从数据库查找相应的基础 DO，再将基础 DO 整合为完整的活动 DTO。
     */
    public ActivityDetailDTO getActivityDetailDTO(Long activityId) {

        // 查表
        ActivityDO activityDO = activityMapper.selectById(activityId);
        List<ActivityPrizeDO> activityPrizeDOList = activityPrizeMapper.selectByActivityId(activityId);
        List<ActivityUserDO> activityUserDOList = activityUserMapper.selectByActivityId(activityId);

        List<Long> prizeIds = activityPrizeDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .distinct()
                .toList();
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);

        return buildActivityDTO(activityDO, prizeDOList, activityPrizeDOList, activityUserDOList);
    }

    /**
     * 将基础 DO 整合为完整的活动 DTO。
     *
     * @param activityDO          活动基础信息
     * @param prizeDOList         奖品基础信息
     * @param activityPrizeDOList 活动奖品关联信息
     * @param activityUserDOList  活动用户关联信息
     * @return activityDetailDTO - 活动全部描述信息
     */
    private ActivityDetailDTO buildActivityDTO(ActivityDO activityDO,
                                               List<PrizeDO> prizeDOList,
                                               List<ActivityPrizeDO> activityPrizeDOList,
                                               List<ActivityUserDO> activityUserDOList) {
        // 整合为完整的活动 DTO
        ActivityDetailDTO activityDetailDTO = new ActivityDetailDTO();

        // set 活动基础信息
        activityDetailDTO.setActivityId(activityDO.getId());
        activityDetailDTO.setActivityName(activityDO.getActivityName());
        activityDetailDTO.setDesc(activityDO.getDescription());
        activityDetailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));

        // set 活动关联的奖品信息
        List<ActivityDetailDTO.ActivityPrizeDTO> prizeDTOList = activityPrizeDOList
                .stream()
                .map(activityPrizeDO -> {

                    ActivityDetailDTO.ActivityPrizeDTO prizeDTO = new ActivityDetailDTO.ActivityPrizeDTO();
                    prizeDTO.setPrizeId(activityPrizeDO.getPrizeId());

                    // 奖品除 ID 外的基本属性在 ActivityPrizeDO 中并不存在，
                    // 此处需要用 prizeId 在 List<PrizeDO> 中查找相应 PrizeDO 的属性，填入 prizeDTO。

                    prizeDOList.forEach(prizeDO -> {
                        if (prizeDO.getId().equals(activityPrizeDO.getPrizeId())) {
                            prizeDTO.setName(prizeDO.getName());
                            prizeDTO.setImageUrl(prizeDO.getImageUrl());
                            prizeDTO.setPrice(prizeDO.getPrice());
                            prizeDTO.setDescription(prizeDO.getDescription());
                        }
                    });

                    prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(activityPrizeDO.getPrizeTiers()));
                    prizeDTO.setPrizeAmount(activityPrizeDO.getPrizeAmount());
                    prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(activityPrizeDO.getStatus()));

                    return prizeDTO;

                }).collect(Collectors.toList());

        activityDetailDTO.setPrizeDTOList(prizeDTOList);

        // set 活动关联的人员信息
        List<ActivityDetailDTO.ActivityUserDTO> userDTOList = activityUserDOList
                .stream()
                .map(activityUserDO -> {

                    ActivityDetailDTO.ActivityUserDTO userDTO = new ActivityDetailDTO.ActivityUserDTO();
                    userDTO.setUserId(activityUserDO.getUserId());
                    userDTO.setUserName(activityUserDO.getUserName());
                    userDTO.setStatus(ActivityUserStatusEnum.forName(activityUserDO.getStatus()));

                    return userDTO;
                }).collect(Collectors.toList());

        activityDetailDTO.setUserDTOList(userDTOList);

        return activityDetailDTO;
    }

    /**
     * 校验参数有效性
     *
     * @param param
     */
    private void checkActivityInfo(CreateActivityParam param) {

        if (param == null) {
            throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_EMPTY);
        }

        // 被关联奖品的 ID 是否均在数据库中
        List<ActivityPrizeParam> prizeList = param.getActivityPrizeList();

        List<Long> prizeIdList = prizeList // List<ActivityPrizeParam> -> List<Long>
                .stream()
                .map(ActivityPrizeParam::getPrizeId)
                .distinct() // 去重
                .toList();

        List<Long> actualPrizeIdList = prizeMapper.selectExistPrizeIds(prizeIdList);

        if (actualPrizeIdList.size() != prizeIdList.size()) {
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
        }

        // 被关联人员的 ID 是否均在数据库中
        List<ActivityUserParam> userList = param.getActivityUserList();

        List<Long> userIdList = userList // List<ActivityUserParam> -> List<Long>
                .stream()
                .map(ActivityUserParam::getUserId)
                .distinct()
                .toList();

        List<Long> actualUserIdList = userMapper.selectExistUserIds(userIdList);

        if (actualUserIdList.size() != userIdList.size()) {
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
        }

        // 奖品总数不能大于人员总数
        long prizeAmountSum = prizeList.stream()
                .mapToLong(ActivityPrizeParam::getPrizeAmount)
                .sum(); // 求和

        if (userIdList.size() < prizeAmountSum) {
            throw new ServiceException(ServiceErrorCodeConstants.PRIZE_USER_AMOUNT_ERROR);
        }

        // 奖品等级有效
        prizeList.forEach(prize -> {
            if (ActivityPrizeTiersEnum.forName(prize.getPrizeTiers()) == null) {
                log.error("奖品等级有误，PrizeTier = {}", prize.getPrizeTiers());
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);
            }
        });
    }
}
